﻿namespace CodeSHARPer.ViewModel.Base
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Windows.Input;
    using Cinch;
    using MEFedMVVM.ViewModelLocator;
    using System.ComponentModel.Composition;

    [ExportViewModel("DialogViewModelBase")]
    [PartCreationPolicy(CreationPolicy.NonShared)]
    public class DialogViewModelBase : Cinch.ViewModelBase
    {
        #region fields
        private bool mShutDownInProgress;
        private bool mIsReadyToClose;

        private ObservableCollection<Msg> mProblems;

        private bool mFoundErrorsInLastRun = true;
        #endregion fields

        #region constructor
        /// <summary>
        /// Standard constructor
        /// </summary>
        public DialogViewModelBase()
        {
            this.InitializeDialogState();
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="copyThis"></param>
        public DialogViewModelBase(DialogViewModelBase copyThis)
        {
            if (copyThis == null) return;

            this.mShutDownInProgress = copyThis.mShutDownInProgress;
            this.mIsReadyToClose = copyThis.mIsReadyToClose;

            CancelCommand = new SimpleCommand<Object, Object>(CanCancel, Cancel);
            OKCommand = new SimpleCommand<Object, Object>(CanOk, Ok);

            this.mProblems = new ObservableCollection<Msg>(copyThis.mProblems);

            this.mFoundErrorsInLastRun = copyThis.mFoundErrorsInLastRun;
        }
        #endregion constructor

        #region delegates
        /// <summary>
        /// This type of method delegation is used when user input is evaluated.
        /// 
        /// The user input is available in the class that instantiates the <seealso cref="DialogViewModelBase"/> class.
        /// Therefore, the <seealso cref="DialogViewModelBase"/> class calls a delegate method to retrieve whether input
        /// is OK or not plus a list messages describing problem details.
        /// </summary>
        /// <param name="outMsg"></param>
        /// <returns></returns>
        public delegate bool EvaluateInput(out List<Msg> outMsg);
        #endregion delegates

        #region properties
        /// <summary>
        /// Get/set proprety to determine whether application is ready to close
        /// (the setter is public here to bind it to a checkbox - in a normal
        /// application that setter would more likely be private and be set via
        /// a corresponding method call that manages/overrides the properties' value).
        /// </summary>
        public bool IsReadyToClose
        {
            get { return this.mIsReadyToClose; }
            set
            {
                if (this.mIsReadyToClose != value)
                {
                    this.mIsReadyToClose = value;
                    NotifyPropertyChanged(ObservableHelper.CreateArgs<DialogViewModelBase>(x => x.IsReadyToClose));
                }
            }
        }

        /// <summary>
        /// This property can be used to delegate the test of user input to the class that instantiates this class.
        /// User input is then, based on the external method, evaluated whenver a user executes the Cancel or OK command.
        /// 
        /// The user input is available in the class that instantiates the <seealso cref="DialogViewModelBase"/> class.
        /// Therefore, the <seealso cref="DialogViewModelBase"/> class calls a delegate method to retrieve whether input
        /// is OK or not plus a list messages describing problem details.
        /// </summary>
        public EvaluateInput EvaluateInputData { get; set; }

        /// <summary>
        /// Execute the cancel command (occurs typically when a user clicks cancel in the dialog)
        /// </summary>
        public SimpleCommand<object, object> CancelCommand { get; private set; }
        /// <summary>
        /// Execute the OK command (occurs typically when a user clicks OK in the dialog)
        /// </summary>
        public SimpleCommand<object, object> OKCommand { get; private set; }

        public void Cancel(Object args)
        {
            this.OnRequestClose(false);
        }

        public bool CanCancel(Object args)
        {
            return true;
        }

        public void Ok(Object args)
        {
            this.OnRequestClose(true);
        }

        public bool CanOk(Object args)
        {
            return this.IsReadyToClose;
        }
        
        /// <summary>
        /// This string can be displayed when the list of problems <seealso cref="ListMessages"/> is displayed.
        /// </summary>
        public string ProblemCaption { get; set; }

        public ObservableCollection<Msg> ListMessages
        {
            get
            {
                if (this.mProblems == null)
                    this.mProblems = new ObservableCollection<Msg>();

                return this.mProblems;
            }
        }
        #endregion properties

        #region methods
        /// <summary>
        /// Reset the viewmodel such that opening a view (dialog) is realized with known states.
        /// </summary>
        public void InitializeDialogState()
        {
            this.ProblemCaption = "Your input is not sufficient. Correct the following problems or click Cancel:";

            this.EvaluateInputData = null;

            this.mIsReadyToClose = true;
            this.mShutDownInProgress = false;

            this.mProblems = new ObservableCollection<Msg>();

            CancelCommand = new SimpleCommand<Object, Object>(CanCancel, Cancel);
            OKCommand = new SimpleCommand<Object, Object>(CanOk, Ok);
        }

        /// <summary>
        /// Method to be executed when user (or program) tries to close the application
        /// </summary>
        public void OnRequestClose(bool setWindowCloseResult)
        {
            try
            {
                if (this.mShutDownInProgress == false)
                {
                        CloseActivePopUpCommand.Execute(setWindowCloseResult);

                    this.mShutDownInProgress = true;
                }
            }
            catch (Exception exp)
            {
                System.Console.WriteLine("Exception occurred in OnRequestClose\n{0}", exp.ToString());
                this.mShutDownInProgress = false;
            }
        }

        public void ClearMessages()
        {
            if (this.mProblems == null)
                this.mProblems = new ObservableCollection<Msg>();

            this.mProblems.Clear();
        }

        public void AddMessage(string strMessage, Msg.MsgCategory categoryOfMsg = Msg.MsgCategory.Error)
        {
            this.AddMessage(new Msg(strMessage, categoryOfMsg));
        }

        public void AddMessage(Msg inputMsg)
        {
            if (this.mProblems == null)
                this.mProblems = new ObservableCollection<Msg>();

            this.mProblems.Add(new Msg(inputMsg));
        }

        /// <summary>
        /// Call the external method delegation (if any) to verify whether user input is valid or not.
        /// </summary>
        private void PerformInputDataEvaluation()
        {
            if (this.EvaluateInputData != null)
            {
                List<Msg> msgs = null;
                bool bResult = this.EvaluateInputData(out msgs);
                bool bFoundErrors = false;

                // Copy messages from delegate method (if any)
                this.ClearMessages();

                if (msgs != null)
                {
                    foreach (Msg m in msgs)
                    {
                        if (m.CategoryOfMsg != Msg.MsgCategory.Information && m.CategoryOfMsg != Msg.MsgCategory.Warning)
                            bFoundErrors = true;

                        this.AddMessage(m);
                    }
                }

                if (bFoundErrors == false)
                {
                    if (this.mFoundErrorsInLastRun == false)
                    {
                        // Found only Information or Warnings for the second time -> lets get over it!
                        this.IsReadyToClose = true;
                        return;
                    }
                    else
                    {
                        this.mFoundErrorsInLastRun = false;
                        this.IsReadyToClose = bResult;
                        return;
                    }
                }

                this.mFoundErrorsInLastRun = true;
                this.IsReadyToClose = bResult;
            }
        }
        #endregion methods
    }
}
